unit fUvMap;

interface

uses
  Winapi.Windows,
  Winapi.Messages,
  System.SysUtils,
  System.Classes,
  System.Math,
  Vcl.Graphics,
  Vcl.Controls,
  Vcl.Forms,
  Vcl.Dialogs,
  Vcl.StdCtrls,
  Vcl.ExtCtrls,
  Vcl.Menus,
  Vcl.ExtDlgs,
  Vcl.ComCtrls,

  GLS.Scene,
  GLS.VectorTypes,
  GLS.PersistentClasses,
  GLS.VectorFileObjects,
  GLS.SceneViewer,
  GLS.VectorLists,
  GLS.VectorGeometry,
  GLS.Texture,
  GLS.Objects,
  GLS.FileOBJ,
  GLS.File3DS,
  GLS.Coordinates,

  GLS.BaseClasses,
  GLS.SimpleNavigation;

type
  TGLS3dUvForm = class(TForm)
    GLScene1: TGLScene;
    GLCamera1: TGLCamera;
    GLFreeForm1: TGLFreeForm;
    ScrollPanel: TPanel;
    GLSceneViewer1: TGLSceneViewer;
    ScrollBox1: TScrollBox;
    Image1: TImage;
    MainMenu1: TMainMenu;
    File1: TMenuItem;
    Map1: TMenuItem;
    N1: TMenuItem;
    ApplyTexture1: TMenuItem;
    miOpen: TMenuItem;
    Save3ds1: TMenuItem;
    N2: TMenuItem;
    Savemeshasbmp1: TMenuItem;
    LoadTexture1: TMenuItem;
    Help1: TMenuItem;
    Help2: TMenuItem;
    About1: TMenuItem;
    Splitter1: TSplitter;
    OpenDialog1: TOpenDialog;
    SaveDialog1: TSaveDialog;
    LaunchPainter1: TMenuItem;
    OpenPictureDialog1: TOpenPictureDialog;
    N256x2561: TMenuItem;
    N512x5121: TMenuItem;
    StatusBar1: TStatusBar;
    Panel1: TPanel;
    CheckBox1: TCheckBox;
    ComboBox2: TComboBox;
    ComboBox1: TComboBox;
    N3: TMenuItem;
    Mesh1: TMenuItem;
    N4: TMenuItem;
    Exit1: TMenuItem;
    GLSimpleNavigation1: TGLSimpleNavigation;
    procedure FormCreate(Sender: TObject);
    procedure GLSceneViewer1MouseMove(Sender: TObject; Shift: TShiftState;
      X, Y: Integer);
    procedure GLSceneViewer1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure ApplyTexture1Click(Sender: TObject);
    procedure miOpenClick(Sender: TObject);
    procedure Save3ds1Click(Sender: TObject);
    procedure LoadTexture1Click(Sender: TObject);
    procedure Savemeshasbmp1Click(Sender: TObject);
    procedure Help2Click(Sender: TObject);
    procedure LaunchPainter1Click(Sender: TObject);
    procedure N256x2561Click(Sender: TObject);
    procedure N512x5121Click(Sender: TObject);
    procedure ComboBox1Change(Sender: TObject);
    procedure ComboBox2Change(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
    procedure Mesh1Click(Sender: TObject);
    procedure Exit1Click(Sender: TObject);
    procedure About1Click(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
  public
    mx, my: Integer;
    procedure GetTexCoordsWireframe;
    procedure DisplayHint(Sender: TObject);
  end;

  TAxis = 0 .. 2;

var
  GLS3dUvForm: TGLS3dUvForm;
  Filename3DS: String;

procedure UVPlanarMapping(Vertices, TexCoords: TGLAffineVectorList;
  Axis: TAxis = 2);
procedure UVCubicMapping(Vertices, TexCoords: TGLAffineVectorList;
  Axis: TAxis = 2);
procedure UVCylindricalMapping(Vertices, TexCoords: TGLAffineVectorList;
  Axis: TAxis = 2);
procedure UVSphericalMapping(Vertices, TexCoords: TGLAffineVectorList;
  Axis: TAxis = 2);

// ===============================================================
implementation
// ===============================================================

uses
  fPainter,
  fMeshEditor,
  fAbout;

{$R *.dfm}

procedure TGLS3dUvForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  //
end;

procedure TGLS3dUvForm.Exit1Click(Sender: TObject);
begin
  Close;
end;

procedure TGLS3dUvForm.FormCreate(Sender: TObject);
var
  s: single;
begin
  Application.OnHint := DisplayHint;
  Image1.Canvas.FillRect(Rect(0, 0, Image1.Width, Image1.Height));
  GLFreeForm1.LoadFromFile('teapot.3ds');
  GLFreeForm1.Material.Texture.Image.LoadFromFile('grid.bmp');
  GLFreeForm1.Material.Texture.Disabled := False;
  GLFreeForm1.Scale.SetVector(0.05, 0.05, 0.05);
  s := 4 / (GLFreeForm1.BoundingSphereRadius);
  GLFreeForm1.Scale.SetVector(s, s, s);
  GLFreeForm1.Pitch(90);
  ComboBox2.Itemindex := 0;
  ComboBox1.Itemindex := 0;
  /// ComboBox1Change(nil);
end;

procedure TGLS3dUvForm.DisplayHint(Sender: TObject);
begin { Both parts if not xx|yyy }
  StatusBar1.SimpleText := GetLongHint(Application.Hint);
end;

procedure UVPlanarMapping(Vertices, TexCoords: TGLAffineVectorList;
  Axis: TAxis = 2);
var
  i, j: Integer;
  P, center, min, max: TAffineVector;
  u, v: single;
begin
  u := 0;
  v := 0; { Compiler shutup }
  for i := 0 to Vertices.Count - 1 do
  begin
    P := Vertices.Items[i];
    for j := 0 to 2 do
    begin
      if i = 0 then
      begin
        min.v[j] := P.v[j];
        max.v[j] := P.v[j];
      end
      else
      begin
        if P.v[j] < min.v[j] then
          min.v[j] := P.v[j];
        if P.v[j] > max.v[j] then
          max.v[j] := P.v[j];
      end;
    end;
  end;
  center := VectorScale(VectorAdd(max, min), 0.5);
  Vertices.Translate(VectorNegate(center));
  min := VectorSubtract(min, center);
  max := VectorSubtract(max, center);

  // Generate texture coordinates
  TexCoords.Clear;
  for i := 0 to Vertices.Count - 1 do
  begin
    P := Vertices.Items[i];
    case Axis of
      0:
        begin
          u := (P.Y / (max.Y - min.Y));
          v := (-P.Z / (max.Z - min.Z));
        end;
      1:
        begin
          u := (P.X / (max.X - min.X));
          v := (-P.Z / (max.Z - min.Z));
        end;
      2:
        begin
          u := (P.X / (max.X - min.X));
          v := (-P.Y / (max.Y - min.Y));
        end;
    end;
    u := u + 0.5;
    v := v + 0.5;
    TexCoords.Add(u, v, 0);
  end;
end;

procedure UVCubicMapping(Vertices, TexCoords: TGLAffineVectorList;
  Axis: TAxis = 2);
begin
  // This one is a little more complicated...I'm working on it
end;

procedure UVCylindricalMapping(Vertices, TexCoords: TGLAffineVectorList;
  Axis: TAxis = 2);
var
  i, j: Integer;
  P, Pn, center, min, max: TAffineVector;
  { temp, } u, v: single;
begin
  u := 0;
  v := 0; { Compiler shutup }
  // Get the center
  for i := 0 to Vertices.Count - 1 do
  begin
    P := Vertices.Items[i];
    for j := 0 to 2 do
    begin
      if i = 0 then
      begin
        min.v[j] := P.v[j];
        max.v[j] := P.v[j];
      end
      else
      begin
        if P.v[j] < min.v[j] then
          min.v[j] := P.v[j];
        if P.v[j] > max.v[j] then
          max.v[j] := P.v[j];
      end;
    end;
  end;
  center := VectorScale(VectorAdd(max, min), 0.5);
  Vertices.Translate(VectorNegate(center));
  min := VectorSubtract(min, center);
  max := VectorSubtract(max, center);

  // Generate texture coordinates
  TexCoords.Clear;
  for i := 0 to Vertices.Count do
  begin
    P := Vertices.Items[i];
    Pn := VectorNormalize(P);
    case Axis of
      0:
        begin
          u := arctan2(Pn.Z, -Pn.Y) / 2;
          v := (P.X / (max.X - min.X));
        end;
      1:
        begin
          u := arctan2(-Pn.X, Pn.Z) / 2;
          v := (P.Y / (max.Y - min.Y));
        end;
      2:
        begin
          u := arctan2(-Pn.X, -Pn.Y) / 2;
          v := (P.Z / (max.Z - min.Z));
        end;
    end;
    u := 0.5 - u / Pi;
    u := u - floor(u);
    v := v + 0.5;
    TexCoords.Add(u, v, 0);
  end;
end;

procedure UVSphericalMapping(Vertices, TexCoords: TGLAffineVectorList;
  Axis: TAxis = 2);
var
  i, j: Integer;
  P, center, min, max: TAffineVector;
  { radius, }
  { temp, } u, v: single;
begin
  u := 0;
  v := 0; { Compiler shutup }
  // Get the center
  for i := 0 to Vertices.Count - 1 do
  begin
    P := Vertices.Items[i];
    for j := 0 to 2 do
    begin
      if i = 0 then
      begin
        min.v[j] := P.v[j];
        max.v[j] := P.v[j];
      end
      else
      begin
        if P.v[j] < min.v[j] then
          min.v[j] := P.v[j];
        if P.v[j] > max.v[j] then
          max.v[j] := P.v[j];
      end;
    end;
  end;
  center := VectorScale(VectorAdd(max, min), 0.5);
  Vertices.Translate(VectorNegate(center));

  // Generate texture coordinates
  TexCoords.Clear;
  for i := 0 to Vertices.Count do
  begin
    P := VectorNormalize(Vertices.Items[i]);
    case Axis of
      0:
        begin
          u := arctan2(P.Z, -P.Y);
          v := arctan(P.X / sqrt(P.Y * P.Y + P.Z * P.Z));
        end;
      1:
        begin
          u := arctan2(-P.X, P.Z);
          v := arctan(P.Y / sqrt(P.X * P.X + P.Z * P.Z));
        end;
      2:
        begin
          u := arctan2(-P.X, -P.Y);
          v := arctan(P.Z / sqrt(P.X * P.X + P.Y * P.Y));
        end;
    end;
    u := 1 - u / (2 * Pi);
    v := abs(0.5 - v / Pi);
    u := u - floor(u);
    TexCoords.Add(u, v, 0);
  end;
end;

procedure TGLS3dUvForm.GLSceneViewer1MouseMove(Sender: TObject;
  Shift: TShiftState; X, Y: Integer);
begin
  if ssLeft in Shift then
    GLCamera1.MoveAroundTarget(my - Y, mx - X);
  mx := X;
  my := Y;
end;

procedure TGLS3dUvForm.GLSceneViewer1MouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  mx := X;
  my := Y;
end;

procedure TGLS3dUvForm.GetTexCoordsWireframe;
var
  i, j, X, Y, x0, y0: Integer;
  fg: TFGVertexIndexList;
begin
  x0 := 0;
  y0 := 0; { Compiler shut up }
  GLS3dUvForm.Image1.Canvas.FillRect(Rect(0, 0, GLS3dUvForm.Image1.Width,
    GLS3dUvForm.Image1.Height));
  GLS3dUvForm.Image1.Canvas.Pen.Color := clBlack;
  with GLS3dUvForm.GLFreeForm1.MeshObjects.Items[0] do
  begin
    for i := 0 to FaceGroups.Count - 1 do
    begin
      fg := TFGVertexIndexList(FaceGroups.Items[i]);
      for j := 0 to fg.VertexIndices.Count - 1 do
      begin
        if j = 0 then
        begin
          x0 := round(TexCoords.Items[fg.VertexIndices.Items[j]].X *
            GLS3dUvForm.Image1.Width);
          y0 := round(TexCoords.Items[fg.VertexIndices.Items[j]].Y *
            GLS3dUvForm.Image1.Height);
          Image1.Canvas.MoveTo(x0, y0);
        end
        else
        begin
          X := round(TexCoords.Items[fg.VertexIndices.Items[j]].X *
            GLS3dUvForm.Image1.Width);
          Y := round(TexCoords.Items[fg.VertexIndices.Items[j]].Y *
            GLS3dUvForm.Image1.Height);
          Image1.Canvas.LineTo(X, Y);
        end;
      end;
      Image1.Canvas.LineTo(x0, y0);
    end;
  end;
end;

procedure TGLS3dUvForm.ApplyTexture1Click(Sender: TObject);
begin
  ApplyTexture1.Checked := (not ApplyTexture1.Checked);
  If ApplyTexture1.Checked then
    GLFreeForm1.Material.Texture.Disabled := False
  else
    GLFreeForm1.Material.Texture.Disabled := True;
end;

procedure TGLS3dUvForm.miOpenClick(Sender: TObject);
var
  s: single;
begin
  If OpenDialog1.Execute then
  begin
    Image1.Canvas.FillRect(Rect(0, 0, Image1.Width, Image1.Height));
    GLFreeForm1.LoadFromFile(OpenDialog1.Filename { 'teapot.3ds' } );
    Filename3DS := OpenDialog1.Filename;
    { Need a Autoscale function here
      GLFreeForm1.Scale.SetVector(0.05,0.05,0.05); }
    s := 4 / (GLFreeForm1.BoundingSphereRadius);
    GLFreeForm1.Scale.SetVector(s, s, s);
    ComboBox1Change(nil);
    GLFreeForm1.Pitch(90);
  end;
end;

procedure TGLS3dUvForm.Save3ds1Click(Sender: TObject);
begin
  { Save as WHATEVER GLSCENE USES that has UV Coords... }
  { SaveDialog1.Filter:='*.obj'; }
  SaveDialog1.Filename := ChangeFileExt(OpenDialog1.Filename, '.obj');
  If SaveDialog1.Execute then
    GLFreeForm1.SaveToFile(SaveDialog1.Filename);
end;

procedure TGLS3dUvForm.LoadTexture1Click(Sender: TObject);
begin
  If OpenPictureDialog1.Execute then
  begin
    GLFreeForm1.Material.Texture.Image.LoadFromFile
      (OpenPictureDialog1.Filename);
    GLFreeForm1.Material.Texture.Disabled := False;
  end;
end;

procedure TGLS3dUvForm.N256x2561Click(Sender: TObject);
begin
  Image1.Picture.Bitmap.Width := 256;
  Image1.Picture.Bitmap.Height := 256;
  Image1.Canvas.FillRect(Rect(0, 0, Image1.Width, Image1.Height));
  GetTexCoordsWireframe;
end;

procedure TGLS3dUvForm.N512x5121Click(Sender: TObject);
begin
  Image1.Picture.Bitmap.Width := 512;
  Image1.Picture.Bitmap.Height := 512;
  Image1.Canvas.FillRect(Rect(0, 0, Image1.Width, Image1.Height));
  GetTexCoordsWireframe;
end;

procedure TGLS3dUvForm.Savemeshasbmp1Click(Sender: TObject);
begin { ? 32 bit for transparency ? or leave it to them? }
  Image1.Picture.Bitmap.PixelFormat := pf24bit;
  Image1.Picture.Bitmap.SaveToFile(ChangeFileExt(Filename3DS, '.bmp'))
end;

procedure TGLS3dUvForm.Help2Click(Sender: TObject);
begin
  ShowMessage('Under Construction');
end;

procedure TGLS3dUvForm.LaunchPainter1Click(Sender: TObject);
begin
  PainterForm.Show;
  PainterForm.Image.Picture.Bitmap := Image1.Picture.Bitmap;
end;

procedure TGLS3dUvForm.ComboBox1Change(Sender: TObject);
begin
  with GLFreeForm1.MeshObjects.Items[0] do
  begin
    case ComboBox2.Itemindex of
      0:
        UVPlanarMapping(Vertices, TexCoords, ComboBox1.Itemindex);
      1:
        UVCubicMapping(Vertices, TexCoords, ComboBox1.Itemindex);
      2:
        UVCylindricalMapping(Vertices, TexCoords, ComboBox1.Itemindex);
      3:
        UVSphericalMapping(Vertices, TexCoords, ComboBox1.Itemindex);
    end;
  end;
  GetTexCoordsWireframe;
  GLFreeForm1.StructureChanged;
end;

procedure TGLS3dUvForm.ComboBox2Change(Sender: TObject);
begin
  ComboBox1Change(nil);
end;

procedure TGLS3dUvForm.CheckBox1Click(Sender: TObject);
begin
  if CheckBox1.Checked then
    GLFreeForm1.NormalsOrientation := mnoInvert
  else
    GLFreeForm1.NormalsOrientation := mnoDefault;
end;

procedure TGLS3dUvForm.Mesh1Click(Sender: TObject);
begin
  FormMeshEditor.Show;
end;

procedure TGLS3dUvForm.About1Click(Sender: TObject);
begin
  AboutBox.ShowModal;
end;

end.
